var ayuta = this["ayuta"] || {};

ayuta.global = this;

( function() {
    // this code is copied from dojo-release-1.1.1. thanks!

    if (!this["console"]) {
        this.console = {
            log : function() {
            } // no-op
        };
    }

    var cn = [ "assert", "count", "debug", "dir", "dirxml", "error", "group",
            "groupEnd", "info", "profile", "profileEnd", "time", "timeEnd",
            "trace", "warn", "log" ];
    var i = 0, tn;
    while ((tn = cn[i++])) {
        if (!console[tn]) {
            ( function() {
                var tcn = tn + "";
                console[tcn] = function() {
                    var a = Array.apply( {}, arguments);
                    a.unshift(tcn + ":");
                    console.log(a.join(" "));
                }
            })();
        }
    }
})();

ayuta.namespace = function(nm) {
    var pkgs = nm.split(".");
    var pkg = ayuta.global;
    for ( var i = 0; i < pkgs.length; i++) {
        var pkgName = pkgs[i];
        if (!pkg[pkgName]) {
            pkg[pkgName] = {};
        }
        pkg = pkg[pkgName];
    }
    return pkg;
};
ayuta.extend = function(dst, src) {
    for ( var i in src)
        dst[i] = src[i];
    return dst;
};
ayuta.clone = function(obj) {
    var cloned = {};
    ayuta.extend(cloned, obj);
    return cloned;
};
ayuta.cloneArray = function(array) {
    var cloned = [];
    for ( var i = 0, n = array.length; i < n; i++) {
        cloned.push(array[i]);
    }
    return cloned;
};
ayuta.toArray = function(obj) {
    if (obj === null || obj === undefined) {
        return obj
    }
    if (obj instanceof Array) {
        return obj;
    }
    if (obj.length !== undefined) {
        return ayuta.cloneArray(obj);
    }
    return [ obj ];
};
ayuta.isEmpty = function(obj) {
    return obj === null || obj === undefined
            || (typeof (obj) == "string" && obj.length == 0)
            || (obj instanceof Array && obj.length == 0);
};
ayuta.isNotEmpty = function(obj) {
    return !ayuta.isEmpty(obj);
};

ayuta.Class = function(definition) {
    var superClasses = definition["superClasses"] || [];
    var clazz = function() {
        this.initialize.apply(this, arguments);
    };
    // inheritance
    for ( var i = 0, n = superClasses.length; i < n; i++) {
        for ( var p in superClasses[i]) {
            if (p != "initialize" && p != "prototype")
                clazz[p] = superClasses[i][p];
        }
        ayuta.extend(clazz.prototype, superClasses[i].prototype);
    }
    ayuta.extend(clazz.prototype, definition);
    delete clazz.prototype.superClasses;
    clazz.prototype.getSuperClasses = function() {
        return superClasses;
    };
    // call constructor of parents before initialize
    clazz.prototype.initialize = function() {
        for ( var i = 0; i < superClasses.length; i++) {
            var superClass = superClasses[i];
            if (superClass.prototype.initialize)
                superClass.prototype.initialize.apply(this, arguments);
        }
        if (definition.initialize) {
            definition.initialize.apply(this, arguments);
        }
    };

    // generate accessors
    var properties = definition["properties"] || {};
    for ( var propertyName in properties) {
        var property = properties[propertyName];
        var privatePropertyName = "_" + propertyName;
        if (property.initialValue !== undefined) {
            if (property.isArray && !(property.initialValue instanceof Array)) {
                throw new Error(
                        "when property type is array, initial value must be array");
            }
            clazz.prototype[privatePropertyName] = property.initialValue;
        } else if (property.isArray) {
            this[privatePropertyName] = [];
        }
        if (typeof (property.get) == "function") {
            clazz.prototype[ayuta.beans.getterName(propertyName,
                    property.isBoolProperty)] = property.get;
        } else {
            clazz.prototype[ayuta.beans.getterName(propertyName,
                    property.isBoolProperty)] = ( function(_propertyName) {
                return function() {
                    return this[_propertyName];
                };
            })(privatePropertyName);
        }
        if (!property.readonly) {
            if (typeof (property.set) == "function") {
                clazz.prototype[ayuta.beans.setterName(propertyName)] = property.set;
            } else {
                clazz.prototype[ayuta.beans.setterName(propertyName)] = ( function(
                        _propertyName) {
                    return function(val) {
                        this[_propertyName] = val;
                    };
                })(privatePropertyName);
            }
        }
        if (property.isArray) {
            clazz.prototype[ayuta.beans.getByIndexMethodName(propertyName)] = ( function(
                    _propertyName) {
                return function(index) {
                    return this[_propertyName][index];
                };
            })(privatePropertyName);

            if (!property.readonly) {
                clazz.prototype[ayuta.beans.setByIndexMethodName(propertyName)] = ( function(
                        _propertyName) {
                    return function(val, index) {
                        this[_propertyName][index] = val;
                    };
                })(privatePropertyName);
                clazz.prototype[ayuta.beans.addMethodName(propertyName)] = ( function(
                        _propertyName) {
                    return function() {
                        var args = (arguments[0] instanceof Array ? arguments[0]
                                : arguments);
                        for ( var i = 0, n = args.length; i < n; i++) {
                            this[_propertyName].push(args[i]);
                        }
                    };
                })(privatePropertyName);
            }
        }
    }
    return clazz;
};

ayuta.beans = {};
ayuta.beans.setterName = function(propertyName) {
    return "set" + ayuta.beans.upFirstLetter(propertyName);
};

ayuta.beans.getterName = function(propertyName, isBoolProperty) {
    var prefix = isBoolProperty ? "is" : "get";
    return prefix + ayuta.beans.upFirstLetter(propertyName);
};
ayuta.beans.setByIndexMethodName = function(propertyName) {
    return ayuta.beans.setterName(propertyName) + "At";
};
ayuta.beans.getByIndexMethodName = function(propertyName) {
    return ayuta.beans.getterName(propertyName) + "At";
};
ayuta.beans.addMethodName = function(propertyName) {
    return "add" + ayuta.beans.upFirstLetter(propertyName);
};

ayuta.beans.upFirstLetter = function(str) {
    return str.charAt(0).toUpperCase() + str.substring(1);
};
ayuta.beans.addNestedPropertyChangeListener = function(obj, propertyName, fn,
        bind) {
    var tokens = propertyName.split(".");
    for ( var i = 0, tokenCount = tokens.length; i < tokenCount; i++) {
        if (i == tokenCount) {
            break;
        }
        ayuta.beans.addPropertyChangeListener(obj, tokens[i], fn, bind);
        obj = ayuta.beans.getProperty(obj, tokens[i]);
    }
};
ayuta.beans.addPropertyChangeListener = function(obj, propertyName, fn, bind) {
    var getter = ayuta.beans.getGetter(obj, propertyName);
    var setter = ayuta.beans.getSetter(obj, propertyName);
    var wrappedSetter = setter;
    bind = bind || obj;
    if (!setter || !setter["_change_listeners"]) {
        wrappedSetter = function(newValue) {
            var oldValue = getter.call(obj);
            setter.call(obj, newValue);
            for ( var i = 0; i < wrappedSetter._change_listeners.length; i++) {
                wrappedSetter._change_listeners[i].apply(bind, [ oldValue,
                        newValue ]);
            }
        };
        obj[ayuta.beans.setterName(propertyName)] = wrappedSetter;
        wrappedSetter._change_listeners = [];
    }
    wrappedSetter._change_listeners.push(fn);
};

ayuta.beans.setNestedProperty = function(obj, propertyName, value, force) {
    var tokens = propertyName.split(".");
    for ( var i = 0, tokenCount = tokens.length; i < tokenCount; i++) {
        if (i == tokenCount - 1) {
            ayuta.beans.setProperty(obj, tokens[i], value, force);
            return;
        } else {
            obj = ayuta.beans.getProperty(obj, tokens[i]);
        }
    }
};
ayuta.beans.getNestedProperty = function(obj, propertyName, force) {
    var tokens = propertyName.split(".");
    for ( var i = 0, tokenCount = tokens.length; i < tokenCount; i++) {
        if (i == tokenCount) {
            break;
        }
        obj = ayuta.beans.getProperty(obj, tokens[i], force);
    }
    return obj;
};

ayuta.beans.setProperty = function(obj, propertyName, value, force) {
    var setter = ayuta.beans.getSetter(obj, propertyName);
    if (setter) {
        setter.call(obj, value);
    } else if (!setter && force) {
        obj[propertyName] = value;
    }
};
ayuta.beans.setIndexedProperty = function(obj, propertyName, index, value) {
    var setter = ayuta.beans.getSetter(obj, propertyName);
    if (!setter) {
        obj[propertyName][index] = value;
    } else {
        setter.call(obj, index, value);
    }
};
ayuta.beans.getProperty = function(obj, propertyName, force) {
    var getter = ayuta.beans.getGetter(obj, propertyName);
    if (!getter) {
        return force ? obj[propertyName] : undefined;
    }
    return getter.call(obj);
};
ayuta.beans.getIndexedProperty = function(obj, propertyName, index) {
    var getter = ayuta.beans.getGetter(obj, propertyName);
    if (!getter) {
        return obj[propertyName][index];
    }
    return getter.call(obj, index);
};
ayuta.beans.getSetter = function(obj, propertyName) {
    return obj[ayuta.beans.setterName(propertyName)];
};
ayuta.beans.getGetter = function(obj, propertyName) {
    var getter = obj[ayuta.beans.getterName(propertyName)];
    if (!getter) {
        getter = obj[ayuta.beans.getterName(propertyName, true)];
    }
    return getter;
};
ayuta.beans.copyProperties = function(src, dest) {
    for ( var p in src) {
        // Getter
        if (p.indexOf("get") !== 0 && p.indexOf("is") !== 0) {
            continue;
        }
        if (typeof src[p] !== "function") {
            continue;
        }
        if (p.indexOf("get") === 0) {
            var prefixLen = 3;
        }
        // isXXX
        else {
            var prefixLen = 2;
        }
        var propertyName = p.substring(prefixLen);
        propertyName = propertyName.charAt(0).toLowerCase() + propertyName.substring(1);

        var propertyValue = ayuta.beans.getProperty(src, propertyName);
        ayuta.beans.setProperty(dest, propertyName, propertyValue);
    }
    return dest;
};

ayuta.namespace("ayuta.errors");

ayuta.errors.define = function(spec) {
    var errors = {};
    for ( var i in spec) {
        var errInfo = spec[i];
        errors[i] = ayuta.extend(new Error(), errInfo);
        errors[i].name = i;
        errors[i].description = errors[i].description || "[" + i + "]";
        errors[i].getInstance = function(optionsOrDescription) {
            if (!optionsOrDescription)
                throw this;
            var err = ayuta.extend(new Error(), this);
            if (typeof optionsOrDescription == "string") {
                err.description = optionsOrDescription;
            } else {
                ayuta.extend(err, optionsOrDescription);
            }
            return err;
        };
        errors[i].raise = function(optionsOrDescription) {
            throw this.getInstance(optionsOrDescription);
        };
    }
    return errors;
};
